/*******************************************************************************
 * Copyright ©  2011 Hulles Industries LLC <hulles.com>
 * All rights reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *     
 *******************************************************************************/
package com.hulles.sancho.engine.entity;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import com.hulles.sancho.engine.Utils;

/**
 * Monitor the creation, alteration and destruction of Sancho database entities.
 * We need this class instead of listening to changes on the entities themselves because the entity objects
 * are ephemeral. This class is a singleton and should not be created explicitly. Instead
 * call getInstance to receive an instantiated EntityMonitor.
 * @author hulles
 *
 */
final public class EntityMonitor {
    private List<EntityMonitorListener> entityListeners;
    private static EntityMonitor instance;
    private EntityType eType;

    private EntityMonitor() {
        entityListeners = new ArrayList<EntityMonitorListener>();
        eType = EntityType.getInstance();
    }

    /**
     * Return an instantiated EntityMonitor class. Call this instead of creating one
     * yourself.
     * @return An instance of EntityMonitor
     */
    public static synchronized EntityMonitor getInstance() {
        if (instance == null) {
            instance = new EntityMonitor();
        }
        return instance;
    }

    /**
     * Notify logged listeners of an update to a SanchoEntity. The entity is responsible
     * for the explicit call. This is not veto-able.
     * @param entity The entity that was updated.
     */
    public synchronized void fireUpdateEntity(SanchoEntity entity) {
        List<EntityMonitorListener> cloneList; // avoid concurrent modifications
        String type;

        if (entity == null) {
            Utils.error("System Error: fireUpdateEntity called with null argument");
            return;
        }
        type = entity.getEntityType();
        Utils.log(Level.FINER, "EntityMonitor: firing entity changed for" + eType.getDisplayName(type));
        cloneList = new ArrayList<EntityMonitorListener>(entityListeners);
        for (Iterator<EntityMonitorListener> i = cloneList.iterator(); i.hasNext();) {
            EntityMonitorListener el = i.next();
            Utils.log(Level.FINEST, "EntityMonitor: notifying listener of entity change");
            el.entityChanged(entity);
        }
        Utils.log(Level.FINER, "EntityMonitor: done notifying listeners of changed entity");
    }

    /**
     * Notify logged listeners of a newly-created SanchoEntity. The entity is responsible
     * for the explicit call. This is not veto-able. Typically the parent needs to know to,
     * for example, refresh child nodes.
     * @param entity The entity that was created.
     * @param parent The entity's parent entity. Can be null for top-level entities.
     */
    public synchronized void fireNewEntity(SanchoEntity entity) {
        List<EntityMonitorListener> cloneList; // avoid concurrent modifications
        String type;

        if (entity == null) {
            Utils.error("System Error: fireNewEntity called with null entity argument");
            return;
        }
        type = entity.getEntityType();
        Utils.log(Level.FINER, "EntityMonitor: firing new entity for entity" + eType.getDisplayName(type));
        cloneList = new ArrayList<EntityMonitorListener>(entityListeners);
        for (Iterator<EntityMonitorListener> i = cloneList.iterator(); i.hasNext();) {
            EntityMonitorListener el = i.next();
            Utils.log(Level.FINEST, "EntityMonitor: notifying listener of new entity");
            el.newEntity(entity);
        }
        Utils.log(Level.FINER, "EntityMonitor: done notifying listeners of new entity");
    }

    /**
     * Notify logged listeners of a deleted SanchoEntity. The entity is responsible
     * for the explicit call. This is not veto-able.
     * @param entity The entity that was deleted.
     */
    public synchronized void fireDeleteEntity(SanchoEntity entity) {
        List<EntityMonitorListener> cloneList; // avoid concurrent modifications
        String type;

        if (entity == null) {
            Utils.error("System Error: fireDeleteEntity called with null argument");
            return;
        }
        type = entity.getEntityType();
        Utils.log(Level.FINER, "EntityMonitor: firing delete entity" + eType.getDisplayName(type));
        cloneList = new ArrayList<EntityMonitorListener>(entityListeners);
        for (Iterator<EntityMonitorListener> i = cloneList.iterator(); i.hasNext();) {
            EntityMonitorListener el = i.next();
            Utils.log(Level.FINEST, "EntityMonitor: notifying listener of delete");
            el.deletedEntity(entity);
        }
        Utils.log(Level.FINER, "EntityMonitor: done notifying listeners of delete entity");
    }

    /**
     * Add a listener to the list of Entity Monitor listeners.
     * @param listener The listener to add.
     */
    public synchronized void addEntityMonitorListener(EntityMonitorListener listener) {
        // TODO: possibly make listeners register for entity type...
        Utils.log(Level.FINER, "EntityMonitor: adding listener");
        if (!entityListeners.contains(listener)) { // sorry, one per customer, buddy...
            entityListeners.add(listener);
        }
    }

    /**
     * Remove a listener from the list of Entity Monitor listeners.
     * @param listener The listener to remove.
     */
    public synchronized void removeEntityMonitorListener(EntityMonitorListener listener) {
        Utils.log(Level.FINER, "EntityMonitor: removing listener");
        entityListeners.remove(listener);
    }

    /**
     * Remove a listener from the list of Entity Monitor listeners.
     */
    public synchronized void removeAllEntityMonitorListeners() {
        Utils.log(Level.FINER, "EntityMonitor: removing all listeners");
        entityListeners.clear();
    }

    /**
     * Return the list of Entity Monitor listeners.
     * @return The list of listeners.
     */
    public synchronized List<EntityMonitorListener> getEntityMonitorListeners() {
        return entityListeners;
    }
}
